/* ***** BEGIN LICENSE BLOCK *****
 *
 * Copyright 2008-2010 Jean-Christophe Sirot <sirot@xulfactory.org>
 *
 * This file is part of jsChessboard.
 *
 * jsChessboard is free software: you can redistribute it and/or modify it under
 * the terms of the GNU General Public License as published by the Free Software
 * Foundation, either version 3 of the License, or (at your option) any later
 * version.
 *
 * jsChessboard is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
 * more details.
 *
 * You should have received a copy of the GNU General Public License along with
 * jsChessboard. If not, see http://www.gnu.org/licenses/.
 *
 * ***** END LICENSE BLOCK *****  */

/**
 * @private
 * This is a temporary bugfix against chromium issue #40931 since that issue
 * is present in the Chrome stable release. This patch will be removed later...
 * http://code.google.com/p/chromium/issues/detail?id=40931
 */
(function () {
    for (var i in "-".split(/-/)) {
        if ( i === "index" ) {
            var oldSplit = String.prototype.split;
            /** @private */
            String.prototype.split = function () {
                var result = oldSplit.apply(this,
Array.prototype.slice.apply(arguments));
                delete result['index'];
                delete result['input'];
                return result;
            }
        }
    }
})()

/**
 * @namespace
 * Global namespace defined by the library.
 * @author <a href="mailto:sirot@xulfactory.org">Jean-Christophe Sirot</a>
 */
var Chessboard = function() {
    /** @inner Default gfx setting */
    var defaultGfx = {
        white: "rgb(251,246,229)",
        black: "rgb(0,127,0)",
        wk: "",
        wq: "",
        wb: "",
        wn: "",
        wr: "",
        wp: "",
        bk: "",
        bq: "",
        bb: "",
        bn: "",
        br: "",
        bp: ""
	};

    /** @inner */
    var parser = {
        /**
         * @private
         * Parses a chess move in short algebraic notation.
         */
        parse: function(move) {
            var mre = new RegExp("^([KQBNR]?)([a-h]?)([1-8]?)(x?)([a-h][1-8])(=([QRBN]))?");
            if (mre.test(move)) {
                var m = mre.exec(move);
                return parser.makeMove(m[1], m[2], m[3], m[4], m[5], m[7]);
            } else {
                throw("Move parsing failed");
            }
        },

        /**
         * @private
         * Gathers all information of a move in a single object.
         * @param {String} piece the piece type (K,Q,R,B,N) or empty string for
         *        the pawn
         * @param {String} fromCol the origin column for disambiguation. May be
         *        undefined
         * @param {String} fromRow the origin row for disambiguation. May be
         *        undefined
         * @param {Boolean} capture indicates if the move captures an opponent
         *        piece
         * @param {String} dest the code of the piece destination square
         *
         * @return the move object
         */
        makeMove: function(piece, fromCol, fromRow, capture, dest, promotion) {
            var move = {};
            if (piece !== "") {
                move.piece = piece;
            } else {
                move.piece = "P";
            }
            if (fromCol !== "") {
                move.fromCol = fromCol.charCodeAt(0) - 96;
            }
            if (fromRow !== "") {
                move.fromRow = fromRow.charCodeAt(0) - 48;
            }
            if (capture !== "") {
                move.capture = true;
            } else {
                move.capture = false;
            }
            if (promotion !== undefined) {
                if (piece !== "" ||
                    (dest.substring(1) !== "1" && dest.substring(1) !== "8"))
                {
                    throw("Move parsing failed");
                }
                move.promotion = promotion;
            }
            move.dest = dest;
            return move;
        }
    };

    /** @inner */
    var fen = {
        /**
         * @private
         */
        decode: function(game, pos) {
            var blacks = new RegExp("[prnbqk]");
            var whites = new RegExp("[PRNBQK]");
            var digits = new RegExp("[1-8]");
            var col = 1, row = 8;
            for (var i = 0; i < pos.length; i++) {
                var c = pos.charAt(i);
                if (digits.test(c)) {
                    col += c.charCodeAt(0) - 48;
                } else if (blacks.test(c)) {
                    game.set("b".concat(c), utils.toSquare(col, row));
                    col++;
                } else if (whites.test(c)) {
                    game.set("w".concat(c.toLowerCase()), utils.toSquare(col, row));
                    col++;
                } else if (c === "/") {
                    row--;
                    col = 1;
                }
            }
        }
    };

    /** @inner */
    var utils = {
        /**
         * @private
         * Converts a column code ['a'-'h'] into an integer [1-8]
         */
        col: function(sq) {
            var col = sq.charCodeAt(0) - 96;
            if (col < 1 || col > 8) {
                throw("Invalid position");
            }
            return col;
        },

        /**
         * @private
         * Converts a row code ['1'-'8'] into an integer [1-8]
         */
        row: function(sq) {
            var row = sq.charCodeAt(1) - 48;
            if (row < 1 || row > 8) {
                throw("Invalid position");
            }
            return row;
        },

        /**
         * @private
         * Converts a couple column, row into a square code
         * @return {String} a square code or undefined if the square does not
         *         exist (row and column out of range)
         */
        toSquare: function(col, row) {
            if (row < 1 || row > 8 || col < 1 || col > 8) {
                return undefined;
            }
            return String.fromCharCode(96 + col, 48 + row);
        },

        /** @private */
        checkSquareValidity: function(sq) {
            var regexp = new RegExp("^[a-h][1-8]");
            if (!regexp.test(sq)) {
                throw("Invalide square code: " + sq);
            }
        }
    };

    /** @inner */
    var gfx = {
        /**
         * @private
         * Draw the chessboard diagram into the target canvas
         * @param {ChessGame} game the position to be drawn
         * @param target the canvas element or the id attribute of the canvas
         *        element
         */
        drawBoard: function(game, target) {
            var canvas;
            if (target instanceof HTMLCanvasElement) {
                 canvas = target;
            } else {
                 canvas = document.getElementById(target);
            }
            if (canvas === undefined || canvas === null) {
                throw ("Target canvas not found: " + target);
            }
            var ratio = canvas.height / 400;
            var ctx = canvas.getContext("2d");
            gfx.initBoard(ctx, ratio);
            for (var sq in game.pieces) {
	        if (game.pieces[sq] !== undefined) {
                    gfx.drawPiece(ctx, ratio, game.pieces[sq], sq);
                }
            }
        },

        /**
         * @private
         * Draw the chessboard without the pieces
         * @param ctx the 2D context of the canvas element
         * @param {Number} the drawing ratio
         */
        initBoard: function(ctx, ratio) {
            var i, j;
            var sz = 50 * ratio;
            ctx.fillStyle = gfx.images.black; //"rgb(0,127,0)";
            for (i = 0; i < 8; i++) {
                for (j = 0; j < 8; j++) {
                    if ((i + j) % 2 === 1) {
                        ctx.fillRect (i*sz, j*sz, sz, sz);
                    }
                }
            }
            ctx.fillStyle = gfx.images.white; //"rgb(251,246,229)";
            for (i = 0; i < 8; i++) {
                for (j = 0; j < 8; j++) {
                    if ((i + j) % 2 === 0) {
                        ctx.fillRect (i*sz, j*sz, sz, sz);
                    }
                }
            }
        },

        /**
         * @private
         * Draw a chess piece
         * @param ctx the 2D context of the canvas element
         * @param {Number} the drawing ratio
         * @param {Piece} piece the piece to be drawn
         * @param {String} sq the code of the square on which the piece is drawn
         */
       drawPiece_async: function(ctx, ratio, piece, sq) {
            /** @inner */
            var mkdraw = function (ctx, img, x, y, s) {
                return function() { ctx.drawImage(img, x, y, s, s); };
            };
            var sz = ratio * 50;
            var x = (sq.charCodeAt(0) - 97) * sz;
            var y = (8 * sz) - (sq.charCodeAt(1) - 48) * sz;
            var img = new Image();
            img.src = gfx.images[piece.code];
            img.onload = mkdraw(ctx, img, x, y, sz);
        },

        /**
         * @private
         * Draw a chess piece
         * @param ctx the 2D context of the canvas element
         * @param {Number} the drawing ratio
         * @param {Piece} piece the piece to be drawn
         * @param {String} sq the code of the square on which the piece is drawn
         */
       drawPiece_sync: function(ctx, ratio, piece, sq) {
            /* for browsers which do not support delayed image loading */
			var sz = ratio * 50;
            x = (sq.charCodeAt(0) - 97) * sz;
            y = (8 * sz) - (sq.charCodeAt(1) - 48) * sz;
            var img = new Image();
            img.src = gfx.images[piece.code];
            ctx.drawImage(img, x, y, sz, sz);
        }
    };

    /** @private */
    var Piece = function(code) {
        this.checkValidity(code);
        this.code = code;
    };

    /** @inner */
    Piece.prototype.type = function() {
        return this.code.charAt(1).toUpperCase();
    };

    /** @inner */
    Piece.prototype.isWhite = function() {
        return this.code.charAt(0).toLowerCase() === "w";
    };

    /** @inner */
    Piece.prototype.isBlack = function() {
        return this.code.charAt(0).toLowerCase() === "b";
    };

    /** @inner */
    Piece.prototype.checkValidity = function(code) {
        var regexp = new RegExp("^[wb][kqbnrp]");
        if (!regexp.test(code)) {
            throw("Invalide piece code: " + code);
        }
    };

    /** @inner */
    var support = {
        _isPlayerColor: function(game, p) {
            return game.playerWhite ? p.isWhite() : p.isBlack();
        },

        /**
         * Move a piece on a chessboard.
         *
         * @function _move
         * @param {ChessGame} game the ChessGame on which the move is applied
         * @param {String} from the code of the origin square
         * @param {String} to the code of the destination square
         * @param {String} the code (Q, B, N, R) of the piece if the move
         *        is a pawn promotion, <code>undefined</code> otherwise.
         */
        _move: function(game, from, to, promotion) {
            var p = game.pieces[from];
            game.pieces[from] = undefined;
            if (promotion === undefined) {
                game.pieces[to] = p;
            } else {
                if (p.isWhite()) {
                    game.set("w" + promotion.toLowerCase(), to);
                } else {
                    game.set("b" + promotion.toLowerCase(), to);
                }
            }
        },

        _checkCapture: function(game, move) {
            if (move.capture) {
                var p = game.pieces[move.dest];
                if (p !== undefined && !this._isPlayerColor(game, p)) {
                    return;
                } else {
                    throw("No piece to capture in " + move.dest);
                }
            }
        },

        _checkEmpty: function(game, move) {
            if (!move.capture) {
                var p = game.pieces[move.dest];
                if (p !== undefined) {
                    throw("Non empty square: " + move.dest);
                }
            }
        }
    };

    /** @inner
     * The callbacks invoked to apply a piece move on a chessboard position.
     */
    var callbacks = {
        /**
         * @private Casteling callback
         * @function O
         */
        O: function(longCasteling) {
            /** @inner */
            var isEmpty = function(game) {
                return function (sq) {
                    var p = game.pieces[sq];
                    return p === undefined;
                };
            }(this);

            if (this.playerWhite) {
                if (longCasteling) {
                    if (!isEmpty("b1") &&
                        !isEmpty("c1") &&
                        !isEmpty("d1"))
                    {
                        throw("Long casteling not authorized");
                    }
                    support._move(this, "e1", "c1");
                    support._move(this, "a1", "d1");
                } else {
                    if (!isEmpty("f1") &&
                        !isEmpty("g1"))
                    {
                        throw("Short casteling not authorized");
                    }
                    support._move(this, "e1", "g1");
                    support._move(this, "h1", "f1");
                }
            } else {
                if (longCasteling) {
                    if (!isEmpty("b8") &&
                        !isEmpty("c8") &&
                        !isEmpty("d8"))
                    {
                        throw("Long casteling not authorized");
                    }
                    support._move(this, "e8", "c8");
                    support._move(this, "a8", "d8");
                } else {
                    if (!isEmpty("f8") &&
                        !isEmpty("g8"))
                    {
                        throw("Short casteling not authorized");
                    }
                    support._move(this, "e8", "g8");
                    support._move(this, "h8", "f8");
                }
            }
        },

        /**
         * @private Pawn move callback
         * @function P
         */
        P: function(move) {
            support._checkCapture(this, move);
            support._checkEmpty(this, move);
            if (this.playerWhite) {
                if (!move.capture) {
                    var c = utils.col(move.dest);
                    var r = utils.row(move.dest);
                    var sq = utils.toSquare(c, r - 1);
                    var p = this.pieces[sq];
                    if (p !== undefined && p.type() === "P" && p.isWhite()) {
                        if (r === 8) {
                            if (move.promotion === undefined) {
                                throw ("Missing pawn promotion");
                            }
                        }
                        support._move(this, sq, move.dest, move.promotion);
                        return;
                    }
                    if (r === 4 && !p) {
                        sq = utils.toSquare(c, r - 2);
                        p = this.pieces[sq];
                        if (p !== undefined && p.type() === "P" && p.isWhite()) {
                            support._move(this, sq, move.dest);
                            return;
                        }
                    }
                    throw ("Pawn cannot move to " + move.dest);
                } else {
                    sq = utils.toSquare(move.fromCol, utils.row(move.dest) - 1);
                    p = this.pieces[sq];
                    var p2 = this.pieces[move.dest];
                    if (p !== undefined && p.type() === "P" && p.isWhite() &&
                            p2 !== undefined && p2.isBlack())
                    {
                        if (r === 8) {
                            if (move.promotion === undefined) {
                                throw ("Missing pawn promotion");
                            }
                        }
                        support._move(this, sq, move.dest, move.promotion);
                        return;
                    }
                    throw ("Pawn cannot move to " + move.dest);
                }
            } else {
                if (!move.capture) {
                    c = utils.col(move.dest);
                    r = utils.row(move.dest);
                    sq = utils.toSquare(c, r + 1);
                    p = this.pieces[sq];
                    if (p !== undefined && p.type() === "P" && p.isBlack()) {
                        if (r === 1) {
                            if (move.promotion === undefined) {
                                throw ("Missing pawn promotion");
                            }
                        }
                        support._move(this, sq, move.dest, move.promotion);
                        return;
                    }
                    if (r === 5 && !p) {
                        sq = utils.toSquare(c, r + 2);
                        p = this.pieces[sq];
                        if (p && p.type() === "P" && p.isBlack()) {
                            support._move(this, sq, move.dest);
                            return;
                        }
                    }
                    throw ("Pawn cannot move to " + move.dest);
                } else {
                    sq = utils.toSquare(move.fromCol, utils.row(move.dest) + 1);
                    p = this.pieces[sq];
                    p2 = this.pieces[move.dest];
                    if (p !== undefined && p.type() === "P" &&
                            p.isBlack() && p2 !== undefined && p2.isWhite())
                    {
                        if (r === 1) {
                            if (move.promotion === undefined) {
                                throw ("Missing pawn promotion");
                            }
                        }
                        support._move(this, sq, move.dest, move.promotion);
                        return;
                    }
                    throw ("Pawn cannot move to " + move.dest);
                }
            }
        },

        /**
         * @private Knight move callback
         * @function N
         */
        N: function(move) {
            support._checkCapture(this, move);
            support._checkEmpty(this, move);
            var cl = utils.col(move.dest);
            var rw = utils.row(move.dest);
            for (var i = -1; i <= 1; i+=2) {
                for (var j = -1; j <= 1; j+=2) {
                    for (var k = 1; k <= 2; k++) {
                        var c = cl + (k * i);
                        var r = rw + (3 - k)* j;
                        var sq = utils.toSquare(c, r);
                        var p = this.pieces[sq];
                        if (p !== undefined && p.type() === "N" &&
                            support._isPlayerColor(this, p))
                        {
                            var from = true;
                            if (move.fromCol !== undefined &&
                                move.fromCol !== utils.col(sq))
                            {
                                from = false;
                            }
                            if (move.fromRow !== undefined &&
                                move.fromRow !== utils.row(sq))
                            {
                                from = false;
                            }
                            if (from) {
                                support._move(this, sq, move.dest);
                                return;
                            }
                        }
                    }
                }
            }
            throw ("Knight cannot move to " + move.dest);
        },

        /**
         * @private Bishop move callback
         * @function B
         */
        B: function(move) {
            support._checkCapture(this, move);
            support._checkEmpty(this, move);
            var cl = utils.col(move.dest);
            var rw = utils.row(move.dest);
            for (var i = -1; i <= 1; i+=2) {
                for (var j = -1; j <= 1; j+=2) {
                    for (var k = 1; k <= 7; k++) {
                        var c = cl + k*i;
                        var r = rw + k*j;
                        var sq = utils.toSquare(c, r);
                        var p = this.pieces[sq];
                        if (p !== undefined) {
                            if (p.type() === "B" && support._isPlayerColor(this, p)) {
                                var from = true;
                                if (move.fromCol !== undefined &&
                                    move.fromCol !== utils.col(sq))
                                {
                                    from = false;
                                }
                                if (move.fromRow !== undefined &&
                                    move.fromRow !== utils.row(sq))
                                {
                                    from = false;
                                }
                                if (from) {
                                    support._move(this, sq, move.dest);
                                    return;
                                }
                            } else {
                                break;
                            }
                        }
                    }
                }
            }
            throw ("Bishop cannot move to " + move.dest);
        },

        /**
         * @private Rook move callback
         * @function R
         */
        R: function(move) {
            support._checkCapture(this, move);
            support._checkEmpty(this, move);
            var cl = utils.col(move.dest);
            var rw = utils.row(move.dest);
            for (var i = 0; i <= 1; i++) {
                for (var j = -1; j <= 1; j+=2) {
                    for (var k = 1; k <= 7; k++) {
                        var c = cl + k*i*j;
                        var r = rw + k*(1-i)*j;
                        var sq = utils.toSquare(c, r);
                        var p = this.pieces[sq];
                        if (p !== undefined) {
                            if (p.type() === "R" && support._isPlayerColor(this, p)) {
                                var from = true;
                                if (move.fromCol !== undefined &&
                                    move.fromCol !== utils.col(sq))
                                {
                                    from = false;
                                }
                                if (move.fromRow !== undefined &&
                                    move.fromRow !== utils.row(sq))
                                {
                                    from = false;
                                }
                                if (from) {
                                    support._move(this, sq, move.dest);
                                    return;
                                }
                            } else {
                                break;
                            }
                        }
                    }
                }
            }
            throw ("Rook cannot move to " + move.dest);
        },

        /**
         * @private Queen move callback
         * @function Q
         */
        Q: function(move) {
            support._checkCapture(this, move);
            support._checkEmpty(this, move);
            var cl = utils.col(move.dest);
            var rw = utils.row(move.dest);
            for (var i = -1; i <= 1; i++) {
                for (var j = -1; j <= 1; j++) {
                    for (var k = 1; k <= 7; k++) {
                        var c = cl + k*i;
                        var r = rw + k*j;
                        var sq = utils.toSquare(c, r);
                        var p = this.pieces[sq];
                        if (p !== undefined) {
                            if (p.type() === "Q" && support._isPlayerColor(this, p)) {
                                var from = true;
                                if (move.fromCol !== undefined &&
                                    move.fromCol !== utils.col(sq))
                                {
                                    from = false;
                                }
                                if (move.fromRow !== undefined &&
                                    move.fromRow !== utils.row(sq))
                                {
                                    from = false;
                                }
                                if (from) {
                                    support._move(this, sq, move.dest);
                                    return;
                                }
                            } else {
                                break;
                            }
                        }
                    }
                }
            }
            throw ("Queen cannot move to " + move.dest);
        },

        /**
         * @private King move callback
         * @function K
         */
        K: function(move) {
            support._checkCapture(this, move);
            support._checkEmpty(this, move);
            var cl = utils.col(move.dest);
            var rw = utils.row(move.dest);
            for (var i = -1; i <= 1; i++) {
                for (var j = -1; j <= 1; j++) {
                    var c = cl + i;
                    var r = rw + j;
                    var sq = utils.toSquare(c, r);
                    var p = this.pieces[sq];
                    if (p !== undefined) {
                        if (p.type() === "K" &&
                            support._isPlayerColor(this, p))
                        {
                            var from = true;
                            if (move.fromCol !== undefined &&
                                move.fromCol !== utils.col(sq))
                            {
                                from = false;
                            }
                            if (move.fromRow !== undefined &&
                                move.fromRow !== utils.row(sq))
                            {
                                from = false;
                            }
                            if (from) {
                                support._move(this, sq, move.dest);
                                return;
                            }
                        } else {
                            break;
                        }
                    }
                }
            }
            throw ("King cannot move to " + move.dest);
        }
    };

    /** @scope Chessboard */
    return {
        /**
         * Creates a new game with all pieces set at their initial position
         * @returns {Chessboard.ChessGame} a new chess game
         */
        newGame: function() {
            if (arguments.length > 0) {
                //alert(arguments[0]);
            }
            var game = new (this.ChessGame)();
            game.reset();
            return game;
        },

        /**
         * Creates a new ChessGame instance. This constructor should not
         * be called directly. Call Chessboard.newGame() to create a new
         * ChessGame.
         *
         * @class This class represents a chess game.
         */
        ChessGame: function() {
            this.playerWhite = true;
            this.pieces = {};
        },

        /**
         * @private
         * This initialization method is called when the script is
         * loaded by the browser.
         */
        initialize: function() {
            /* Initialize gfx */
            gfx.images = defaultGfx;
            if (window.opera !== undefined) {
                gfx.drawPiece = gfx.drawPiece_sync;
            } else {
                gfx.drawPiece = gfx.drawPiece_async;
            }

            this.ChessGame.prototype = {
                /**
                 * Resets the game to the initial chessboard position.
                 * @method reset
                 */
                reset: function() {
                    this.pieces = {};
                    this.playerWhite = true;
                    this.set("wk", "e1");
                    this.set("bk", "e8");

                    this.set("wq", "d1");
                    this.set("bq", "d8");

                    this.set("wb", "c1");
                    this.set("wb", "f1");
                    this.set("bb", "c8");
                    this.set("bb", "f8");

                    this.set("wn", "b1");
                    this.set("wn", "g1");
                    this.set("bn", "b8");
                    this.set("bn", "g8");

                    this.set("wr", "a1");
                    this.set("wr", "h1");
                    this.set("br", "a8");
                    this.set("br", "h8");

                    this.set("wp", "a2");
                    this.set("wp", "b2");
                    this.set("wp", "c2");
                    this.set("wp", "d2");
                    this.set("wp", "e2");
                    this.set("wp", "f2");
                    this.set("wp", "g2");
                    this.set("wp", "h2");
                    this.set("bp", "a7");
                    this.set("bp", "b7");
                    this.set("bp", "c7");
                    this.set("bp", "d7");
                    this.set("bp", "e7");
                    this.set("bp", "f7");
                    this.set("bp", "g7");
                    this.set("bp", "h7");
                },

                /**
                 * Removes all pieces or clear a single square of the chessboard.
                 */
                clear: function() {
                    if (arguments.length > 0) {
                        var sq = arguments[0];
                        utils.checkSquareValidity(sq);
                        this.pieces[sq] = undefined;
                    } else {
                        this.pieces = [];
                    }
                },

                /**
                 * Sets a piece on a square of the chessboard.
                 * <p>The piece code is a String of the form XY where:</p>
                 * <ul>
                 *   <li>X is the piece color:
                 *     <ul>
                 *       <li>'w' for white
                 *       <li>'b' for black
                 *     </ul>
                 *   </li>
                 *   <li>Y is the piece type
                 *     <ul>
                 *       <li>'k' for King
                 *       <li>'q' for Queen
                 *       <li>'r' for Rook
                 *       <li>'b' for Bishop
                 *       <li>'n' for Knight
                 *       <li>'p' for Pawn
                 *     </ul>
                 *   </li>
                 * </ul>
                 *
                 * <p>The square code uses the traditional chess square
                 * notation: 'a1', 'f6' or 'h8' are valid squares.</p>
                 *
                 * <p>Examples:</p>
                 * <ul>
                 *   <li><pre>set('wq', 'd1')</pre> puts a white queen on square 'd1'</li>
                 *   <li><pre>set('bn', 'c6')</pre> puts a black knight on square 'c6'</li>
                 * </ul>
                 *
                 * @method set
                 * @param {String} code the code of the piece to set.
                 * @param {String} sq the code
                 *
                 */
                set: function(code, sq) {
                    utils.checkSquareValidity(sq);
                    this.pieces[sq] = new Piece(code);
                },

                /**
                 * Clears the chessboard and sets the chessboard using the
                 * Forsyth-Edwards notation (FEN).
                 *
                 * @param {String} fenPosition the position using Forsyth-Edwards notation.
                 * @see <a href="http://en.wikipedia.org/wiki/FEN">FEN notation details</a>
                 */
                setFen: function(fenPosition) {
                    this.clear();
                    fen.decode(this, fenPosition);
                },

                /**
                 * Changes the current player to white.
                 */
                setWhite: function() {
                    this.playerWhite = true;
                },

                /**
                 * Changes the current player to black.
                 */
                setBlack: function() {
                    this.playerWhite = false;
                },

                /**
                 * Applies a sequence of piece moves to the current position.
                 * <p>Code sample:</p>
                 * <pre>
                 * var game = Chessboard.newGame();
                 * game.move("e4, e5, Nf3");
                 * game.draw("myboard");
                 * </pre>
                 *
                 * @method move
                 * @param {String} moves a comma separated list of
                 *        moves in the short algebraic notation.
                 */
                move: function(moves) {
                    var re = new RegExp("\\s*,\\s*");
                    var splits = moves.split(re);
                    for (var i in splits) {
                        var mv = splits[i];
                        //~ alert(mv);
                        try {
                            if (mv === "O-O-O") {
                                callbacks.O.call(this, true);
                            } else if (mv === "O-O") {
                                callbacks.O.call(this, false);
                            } else {
                                var move = parser.parse(mv);
                                var fct = callbacks[move.piece];
                                fct.call(this, move);
                            }
                            this.playerWhite = !this.playerWhite;
                        } catch (e) {
                            throw("Invalid move: " + mv + "\n" + e);
                        }
                    }
                },

                /**
                 * Draw a chessboard diagram representing the current
                 * game position.
                 * @param target either the canvas element on
                 *        which the chessboard must be drawn or the DOM id
                 *        attribute of the canvas element.
                 */
                draw: function(target) {
                    gfx.drawBoard(this, target);
                }
            };
        }
    };
}();

Chessboard.initialize();
